﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Kent.Boogaart.Gauntlet;
using Kent.Boogaart.Gauntlet.Diagnostics;
using Moq;
using NUnit.Framework;

namespace Kent.Boogaart.UnitTest.Gauntlet.Diagnostics
{
	[TestFixture]
	public sealed class TraceSourceLogTest
	{
		private TraceSourceLog _log;

		[SetUp]
		public void SetUp()
		{
			_log = new TraceSourceLog("LogTest");
			//default to all levels
			_log.TraceSource.Switch.Level = SourceLevels.All;
		}

		[TearDown]
		public void TearDown()
		{
			//make sure any mock listeners are removed from the AppDomain-wide collection
			Trace.Listeners.Clear();
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Ctor_ShouldThrowIfNullName()
		{
			_log = new TraceSourceLog(null);
		}

		[Test]
		public void IsVerboseEnabled_ShouldBeFalseIfSwitchDisablesIt()
		{
			_log.TraceSource.Switch.Level = System.Diagnostics.SourceLevels.Off;
			Assert.IsFalse(_log.IsVerboseEnabled);
		}

		[Test]
		public void IsVerboseEnabled_ShouldBeTrueIfSwitchEnablesIt()
		{
			_log.TraceSource.Switch.Level = System.Diagnostics.SourceLevels.Verbose;
			Assert.IsTrue(_log.IsVerboseEnabled);
		}

		[Test]
		public void IsInformationEnabled_ShouldBeFalseIfSwitchDisablesIt()
		{
			_log.TraceSource.Switch.Level = System.Diagnostics.SourceLevels.Off;
			Assert.IsFalse(_log.IsInformationEnabled);
		}

		[Test]
		public void IsInformationEnabled_ShouldBeTrueIfSwitchEnablesIt()
		{
			_log.TraceSource.Switch.Level = System.Diagnostics.SourceLevels.Information;
			Assert.IsTrue(_log.IsInformationEnabled);
		}

		[Test]
		public void IsWarningEnabled_ShouldBeFalseIfSwitchDisablesIt()
		{
			_log.TraceSource.Switch.Level = System.Diagnostics.SourceLevels.Off;
			Assert.IsFalse(_log.IsWarningEnabled);
		}

		[Test]
		public void IsWarningEnabled_ShouldBeTrueIfSwitchEnablesIt()
		{
			_log.TraceSource.Switch.Level = System.Diagnostics.SourceLevels.Information;
			Assert.IsTrue(_log.IsWarningEnabled);
		}

		[Test]
		public void IsErrorEnabled_ShouldBeFalseIfSwitchDisablesIt()
		{
			_log.TraceSource.Switch.Level = System.Diagnostics.SourceLevels.Off;
			Assert.IsFalse(_log.IsErrorEnabled);
		}

		[Test]
		public void IsErrorEnabled_ShouldBeTrueIfSwitchEnablesIt()
		{
			_log.TraceSource.Switch.Level = System.Diagnostics.SourceLevels.Information;
			Assert.IsTrue(_log.IsErrorEnabled);
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Verbose_ShouldThrowIfNullMessage()
		{
			_log.Verbose(null);
		}

		[Test]
		public void Verbose_ShouldWriteMessageToListeners()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();
			traceListener.Expect(x => x.TraceEvent(It.IsAny<TraceEventCache>(), It.IsAny<string>(), TraceEventType.Verbose, It.IsAny<int>(), "A message")).Verifiable();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			_log.Verbose("A message");
			traceListener.Verify();
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Verbose_ShouldThrowIfNullFormat()
		{
			_log.Verbose(null, 1, 2, 3);
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Verbose_ShouldThrowIfNullParamsArray()
		{
			_log.Verbose(string.Empty, (object[])null);
		}

		[Test]
		public void Verbose_ShouldWriteFormattedMessageToListeners()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();
			traceListener.Expect(x => x.TraceEvent(It.IsAny<TraceEventCache>(), It.IsAny<string>(), TraceEventType.Verbose, It.IsAny<int>(), "A formatted message: {0}, {1}", It.IsAny<object[]>())).Verifiable();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			_log.Verbose("A formatted message: {0}, {1}", 1, "hello");
			traceListener.Verify();
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Information_ShouldThrowIfNullMessage()
		{
			_log.Information(null);
		}

		[Test]
		public void Information_ShouldWriteMessageToListeners()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();
			traceListener.Expect(x => x.TraceEvent(It.IsAny<TraceEventCache>(), It.IsAny<string>(), TraceEventType.Information, It.IsAny<int>(), "A message")).Verifiable();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			_log.Information("A message");
			traceListener.Verify();
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Information_ShouldThrowIfNullFormat()
		{
			_log.Information(null, 1, 2, 3);
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Information_ShouldThrowIfNullParamsArray()
		{
			_log.Information(string.Empty, (object[])null);
		}

		[Test]
		public void Information_ShouldWriteFormattedMessageToListeners()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();
			traceListener.Expect(x => x.TraceEvent(It.IsAny<TraceEventCache>(), It.IsAny<string>(), TraceEventType.Information, It.IsAny<int>(), "A formatted message: {0}, {1}", It.IsAny<object[]>())).Verifiable();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			_log.Information("A formatted message: {0}, {1}", 1, "hello");
			traceListener.Verify();
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Warning_ShouldThrowIfNullMessage()
		{
			_log.Warning(null);
		}

		[Test]
		public void Warning_ShouldWriteMessageToListeners()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();
			traceListener.Expect(x => x.TraceEvent(It.IsAny<TraceEventCache>(), It.IsAny<string>(), TraceEventType.Warning, It.IsAny<int>(), "A message")).Verifiable();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			_log.Warning("A message");
			traceListener.Verify();
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Warning_ShouldThrowIfNullFormat()
		{
			_log.Warning(null, 1, 2, 3);
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Warning_ShouldThrowIfNullParamsArray()
		{
			_log.Warning(string.Empty, (object[])null);
		}

		[Test]
		public void Warning_ShouldWriteFormattedMessageToListeners()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();
			traceListener.Expect(x => x.TraceEvent(It.IsAny<TraceEventCache>(), It.IsAny<string>(), TraceEventType.Warning, It.IsAny<int>(), "A formatted message: {0}, {1}", It.IsAny<object[]>())).Verifiable();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			_log.Warning("A formatted message: {0}, {1}", 1, "hello");
			traceListener.Verify();
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Error_ShouldThrowIfNullMessage()
		{
			_log.Error(null);
		}

		[Test]
		public void Error_ShouldWriteMessageToListeners()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();
			traceListener.Expect(x => x.TraceEvent(It.IsAny<TraceEventCache>(), It.IsAny<string>(), TraceEventType.Error, It.IsAny<int>(), "A message")).Verifiable();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			_log.Error("A message");
			traceListener.Verify();
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Error_ShouldThrowIfNullFormat()
		{
			_log.Error(null, 1, 2, 3);
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Error_ShouldThrowIfNullParamsArray()
		{
			_log.Error(string.Empty, (object[])null);
		}

		[Test]
		public void Error_ShouldWriteFormattedMessageToListeners()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();
			traceListener.Expect(x => x.TraceEvent(It.IsAny<TraceEventCache>(), It.IsAny<string>(), TraceEventType.Error, It.IsAny<int>(), "A formatted message: {0}, {1}", It.IsAny<object[]>())).Verifiable();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			_log.Error("A formatted message: {0}, {1}", 1, "hello");
			traceListener.Verify();
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Exception_ShouldThrowIfExceptionIsNull()
		{
			_log.Exception(null);
		}

		[Test]
		public void Exception_ShouldLogExceptionDetails()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();
			traceListener.Expect(x => x.TraceEvent(It.IsAny<TraceEventCache>(), It.IsAny<string>(), TraceEventType.Error, It.IsAny<int>(), It.Is<string>(msg => msg.Contains("System.Exception : A message")))).Verifiable();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			try
			{
				//throwing ensures we get a stack trace
				throw new Exception("A message");
			}
			catch (Exception ex)
			{
				_log.Exception(ex);
			}

			traceListener.Verify();
		}

		[Test]
		public void Exception_ShouldLogInnerExceptionDetails()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();
			traceListener.Expect(x => x.TraceEvent(It.IsAny<TraceEventCache>(), It.IsAny<string>(), TraceEventType.Error, It.IsAny<int>(), It.Is<string>(msg => msg.Contains("System.Exception : Inner exception")))).Verifiable();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			try
			{
				try
				{
					throw new Exception("A message");
				}
				catch (Exception ex)
				{
					throw new Exception("Inner exception", ex);
				}
			}
			catch (Exception ex)
			{
				_log.Exception(ex);
			}

			traceListener.Verify();
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Exception_WithLevel_ShouldThrowIfExceptionIsNull()
		{
			_log.Exception(LogLevel.Verbose, null);
		}

		[Test]
		[ExpectedException(typeof(ArgumentException), "Enum value '1000' is not defined for enumeration 'Kent.Boogaart.Gauntlet.LogLevel'.\r\nParameter name: level")]
		public void Exception_WithLevel_ShouldThrowIfLogLevelIsInvalid()
		{
			_log.Exception((LogLevel)1000, new Exception());
		}

		[Test]
		public void Exception_WithLevel_ShouldLogAtSpecifiedLevel()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();
			traceListener.Expect(x => x.TraceEvent(It.IsAny<TraceEventCache>(), It.IsAny<string>(), TraceEventType.Information, It.IsAny<int>(), It.Is<string>(msg => msg.Contains("System.Exception : A message")))).Verifiable();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			try
			{
				//throwing ensures we get a stack trace
				throw new Exception("A message");
			}
			catch (Exception ex)
			{
				_log.Exception(LogLevel.Information, ex);
			}

			traceListener.Verify();
		}

		[Test]
		public void Exception_WithLeadingMessage_ShouldAllowNullLeadingMessage()
		{
			try
			{
				//throwing ensures we get a stack trace
				throw new Exception("A message");
			}
			catch (Exception ex)
			{
				_log.Exception(ex, null);
			}
		}

		[Test]
		public void Exception_WithLeadingMessage_ShouldLogWithLeadingMessage()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();
			traceListener.Expect(x => x.TraceEvent(It.IsAny<TraceEventCache>(), It.IsAny<string>(), TraceEventType.Error, It.IsAny<int>(), It.Is<string>(msg => msg.Contains("Leading message\r\nSystem.Exception : A message")))).Verifiable();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			try
			{
				//throwing ensures we get a stack trace
				throw new Exception("A message");
			}
			catch (Exception ex)
			{
				_log.Exception(ex, "Leading message");
			}

			traceListener.Verify();
		}

		[Test]
		public void Exception_WithLeadingFormattedMessage_ShouldLogWithFormattedMessage()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();
			traceListener.Expect(x => x.TraceEvent(It.IsAny<TraceEventCache>(), It.IsAny<string>(), TraceEventType.Error, It.IsAny<int>(), It.Is<string>(msg => msg.Contains("Leading message: arg\r\nSystem.Exception : A message")))).Verifiable();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			try
			{
				//throwing ensures we get a stack trace
				throw new Exception("A message");
			}
			catch (Exception ex)
			{
				_log.Exception(ex, "Leading message: {0}", "arg");
			}

			traceListener.Verify();
		}

		[Test]
		public void Exception_WithLevelAndLeadingFormattedMessage_ShouldLogWithFormattedMessageAtSpecifiedLevel()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();
			traceListener.Expect(x => x.TraceEvent(It.IsAny<TraceEventCache>(), It.IsAny<string>(), TraceEventType.Information, It.IsAny<int>(), It.Is<string>(msg => msg.Contains("Leading message: arg\r\nSystem.Exception : A message")))).Verifiable();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			try
			{
				//throwing ensures we get a stack trace
				throw new Exception("A message");
			}
			catch (Exception ex)
			{
				_log.Exception(LogLevel.Information, ex, "Leading message: {0}", "arg");
			}

			traceListener.Verify();
		}

		[Test]
		[ExpectedException(typeof(ArgumentException))]
		public void Performance_ShouldThrowIfNullMessage()
		{
			_log.Performance(null);
		}

		[Test]
		[ExpectedException(typeof(ArgumentException))]
		public void Performance_ShouldThrowIfEmptyMessage()
		{
			_log.Performance(string.Empty);
		}

		[Test]
		[ExpectedException(typeof(ArgumentException))]
		public void Performance_ShouldThrowIfBlankMessage()
		{
			_log.Performance("  ");
		}

		[Test]
		public void Performance_ShouldntWriteMessageToListenersIfNotDisposed()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			_log.Performance("A message");
			traceListener.Verify();
		}

		[Test]
		public void Performance_ShouldWriteMessageToListenersWhenDisposed()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();
			traceListener.Expect(x => x.TraceEvent(It.IsAny<TraceEventCache>(), It.IsAny<string>(), TraceEventType.Information, It.IsAny<int>(), "'{0}' took {1} to execute ({2}ms)", It.IsAny<object[]>())).Verifiable();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			_log.Performance("A message").Dispose();
			traceListener.Verify();
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Performance_ShouldThrowIfNullFormat()
		{
			_log.Performance(null, 1, 2, 3);
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Performance_ShouldThrowIfNullParamsArray()
		{
			_log.Performance(string.Empty, (object[])null);
		}

		[Test]
		public void Performance_ShouldWriteFormattedMessageToListenersIfNotDisposed()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			_log.Performance("A formatted message: {0}, {1}", 1, "hello");
			traceListener.Verify();
		}

		[Test]
		public void Performance_ShouldWriteFormattedMessageToListenersIfDisposed()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();
			traceListener.Expect(x => x.TraceEvent(It.IsAny<TraceEventCache>(), It.IsAny<string>(), TraceEventType.Information, It.IsAny<int>(), "'{0}' took {1} to execute ({2}ms)", It.IsAny<object[]>())).Verifiable();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			_log.Performance("Format {0}", 1).Dispose();
			traceListener.Verify();
		}

		[Test]
		[ExpectedException(typeof(ArgumentException), "Enum value '1000' is not defined for enumeration 'Kent.Boogaart.Gauntlet.LogLevel'.\r\nParameter name: level")]
		public void ShouldWrite_ShouldThrowIfInvalidEnumMember()
		{
			_log.ShouldWrite((LogLevel)1000);
		}

		[Test]
		public void ShouldWrite_ShouldReturnTrueOnlyIfLevelEnabled()
		{
			List<LogLevel> logLevels = new List<LogLevel>();

			foreach (LogLevel logLevel in Enum.GetValues(typeof(LogLevel)))
			{
				logLevels.Add(logLevel);
			}

			//all levels have been enabled by default
			ShouldWrite_Helper(logLevels);

			//remove verbose and everything else should be enabled
			_log.TraceSource.Switch.Level = SourceLevels.Information;
			logLevels.Remove(LogLevel.Verbose);
			ShouldWrite_Helper(logLevels);

			//remove information and everything above should be enabled
			_log.TraceSource.Switch.Level = SourceLevels.Warning;
			logLevels.Remove(LogLevel.Information);
			ShouldWrite_Helper(logLevels);

			//remove warning and everything above should be enabled
			_log.TraceSource.Switch.Level = SourceLevels.Error;
			logLevels.Remove(LogLevel.Warning);
			ShouldWrite_Helper(logLevels);

			//remove error and everything above should be enabled
			_log.TraceSource.Switch.Level = SourceLevels.Critical;
			logLevels.Remove(LogLevel.Error);
			ShouldWrite_Helper(logLevels);

			//remove critical and nothing should be enabled
			_log.TraceSource.Switch.Level = SourceLevels.Off;
			logLevels.Remove(LogLevel.Critical);
			ShouldWrite_Helper(logLevels);
		}

		private void ShouldWrite_Helper(List<LogLevel> enabledLevels)
		{
			foreach (LogLevel logLevel in Enum.GetValues(typeof(LogLevel)))
			{
				if (enabledLevels.Contains(logLevel))
				{
					Assert.IsTrue(_log.ShouldWrite(logLevel));
				}
				else
				{
					Assert.IsFalse(_log.ShouldWrite(logLevel));
				}
			}
		}

		[Test]
		[ExpectedException(typeof(ArgumentException), "Enum value '1000' is not defined for enumeration 'Kent.Boogaart.Gauntlet.LogLevel'.\r\nParameter name: level")]
		public void Write_ShouldThrowIfInvalidLevel()
		{
			_log.Write((LogLevel)1000, string.Empty);
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Write_ShouldThrowIfNullMessage()
		{
			_log.Write(LogLevel.Critical, null);
		}

		[Test]
		public void Write_ShouldWriteMessageToListeners()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();
			traceListener.Expect(x => x.TraceEvent(It.IsAny<TraceEventCache>(), It.IsAny<string>(), TraceEventType.Critical, It.IsAny<int>(), "A message")).Verifiable();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			_log.Write(LogLevel.Critical, "A message");
			traceListener.Verify();
		}

		[Test]
		[ExpectedException(typeof(ArgumentException), "Enum value '1000' is not defined for enumeration 'Kent.Boogaart.Gauntlet.LogLevel'.\r\nParameter name: level")]
		public void Write_Formatted_ShouldThrowIfInvalidLevel()
		{
			_log.Write((LogLevel)1000, string.Empty, 1, 2, 3);
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Write_ShouldThrowIfNullFormat()
		{
			_log.Write(LogLevel.Critical, null, 1, 2, 3);
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Write_ShouldThrowIfNullParamsArray()
		{
			_log.Write(LogLevel.Critical, string.Empty, (object[])null);
		}

		[Test]
		public void Write_ShouldWriteFormattedMessageToListeners()
		{
			Mock<TraceListener> traceListener = new Mock<TraceListener>();
			traceListener.Expect(x => x.TraceEvent(It.IsAny<TraceEventCache>(), It.IsAny<string>(), TraceEventType.Critical, It.IsAny<int>(), "A message {0} {1} {2}", It.IsAny<object[]>())).Verifiable();

			_log.TraceSource.Listeners.Clear();
			_log.TraceSource.Listeners.Add(traceListener.Object);

			_log.Write(LogLevel.Critical, "A message {0} {1} {2}", 1, 2, 3);
			traceListener.Verify();
		}

		[Test]
		[ExpectedException(typeof(ArgumentException))]
		public void PushLogicalOperation_ShouldThrowIfNull()
		{
			_log.PushLogicalOperation(null);
		}

		[Test]
		[ExpectedException(typeof(ArgumentException))]
		public void PushLogicalOperation_ShouldThrowIfEmpty()
		{
			_log.PushLogicalOperation(string.Empty);
		}

		[Test]
		[ExpectedException(typeof(ArgumentException))]
		public void PushLogicalOperation_ShouldThrowIfBlank()
		{
			_log.PushLogicalOperation("   ");
		}

		[Test]
		public void PushLogicalOperation_ShouldReturnDisposable()
		{
			Assert.IsNotNull(_log.PushLogicalOperation("name"));
			//have to pop it off because the stack is stored against the thread - if we don't pop we'll affect the other tests
			_log.PopLogicalOperation();
		}

		[Test]
		public void PushLogicalOperation_ShouldMaintainStackOfOperations()
		{
			Stack logicalOperationStack = System.Diagnostics.Trace.CorrelationManager.LogicalOperationStack;

			Assert.AreEqual(0, logicalOperationStack.Count);
			_log.PushLogicalOperation("op1");
			Assert.AreEqual(1, logicalOperationStack.Count);
			Assert.AreEqual("op1", logicalOperationStack.Peek());

			_log.PushLogicalOperation("op2");
			Assert.AreEqual(2, logicalOperationStack.Count);
			Assert.AreEqual("op2", logicalOperationStack.Peek());

			_log.PushLogicalOperation("op3");
			Assert.AreEqual(3, logicalOperationStack.Count);
			Assert.AreEqual("op3", logicalOperationStack.Peek());

			_log.PopLogicalOperation();
			Assert.AreEqual(2, logicalOperationStack.Count);
			Assert.AreEqual("op2", logicalOperationStack.Peek());

			_log.PopLogicalOperation();
			Assert.AreEqual(1, logicalOperationStack.Count);
			Assert.AreEqual("op1", logicalOperationStack.Peek());

			_log.PopLogicalOperation();
			Assert.AreEqual(0, logicalOperationStack.Count);
		}

		[Test]
		public void PushLogicalOperation_ShouldWorkWithUsing()
		{
			Stack logicalOperationStack = System.Diagnostics.Trace.CorrelationManager.LogicalOperationStack;

			using (_log.PushLogicalOperation("op1"))
			using (_log.PushLogicalOperation("op2"))
			using (_log.PushLogicalOperation("op3"))
			{
				Assert.AreEqual(3, logicalOperationStack.Count);
			}

			Assert.AreEqual(0, logicalOperationStack.Count);
		}

		[Test]
		public void PushLogicalOperation_ShouldBeAbleToDisposeMultipleTimes()
		{
			Stack logicalOperationStack = System.Diagnostics.Trace.CorrelationManager.LogicalOperationStack;

			IDisposable cookie = _log.PushLogicalOperation("op");
			Assert.AreEqual(1, logicalOperationStack.Count);
			cookie.Dispose();
			Assert.AreEqual(0, logicalOperationStack.Count);

			//should be able to re-invoke dispose without any problems (ie. it won't pop the stack again)
			cookie.Dispose();
			cookie.Dispose();
			cookie.Dispose();
			cookie.Dispose();
			Assert.AreEqual(0, logicalOperationStack.Count);
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void PushLogicalOperation_ShouldThrowIfFormatNull()
		{
			_log.PushLogicalOperation(null, 1, 2, 3);
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void PushLogicalOperation_ShouldThrowIfParamsArgsNull()
		{
			_log.PushLogicalOperation("name", (object[])null);
		}

		[Test]
		public void PushLogicalOperation_ShouldFormatOperationName()
		{
			Stack logicalOperationStack = System.Diagnostics.Trace.CorrelationManager.LogicalOperationStack;

			using (_log.PushLogicalOperation("operation named '{0}'", "something"))
			{
				Assert.AreEqual("operation named 'something'", logicalOperationStack.Peek());
			}
		}

		[Test]
		[ExpectedException(typeof(InvalidOperationException))]
		public void PopLogicalOperation_ShouldThrowIfNoLogicalOperationsOnStack()
		{
			_log.PopLogicalOperation();
		}
	}
}